[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください
Scalaを使っているとたまに聞く「implicit conversion」「暗黙の型変換」というキーワード。
なんのことやらよくわからない、という人も多いと思います。
この記事ではそんな暗黙の型変換について解説していきます。
暗黙の型変換とは
使えないはずのメソッドを使えるようにしたり、合わないはずの型を合わせたりする便利機能
- 特定の型
Bが求められているところに別の型Aが使われていて、本来であれば型が合わずにコンパイルエラーになるべきところ、 - スコープ内にあらかじめ
AをBに変換できるメソッドfが定義されていて、- かつ、その使うべき変換メソッドが一意に定まる場合には(つまり、他にも複数あって迷う、というようなことがない場合)、
- その型
Aをメソッドfを使って自動的に別の型Bに変換する
という機能です。
合わない型をコンパイラがいい感じに(つまり、暗黙的に)別の型へと変換してくれる仕組みです。
暗黙の型変換でメソッドチェーンを実現する
Scala 2.13より導入されたtapメソッドについて見てみましょう。
tapメソッドを使う
tapメソッドとは、処理の途中に副作用を挟むためのメソッドです。
1.tap(n => println(s"tapped $n"))
さて、1はIntです。
実はIntはtapメソッドなどというものは持っていません。
しかしながら、scala.util.chainingパッケージの中身をインポートすると、1にtapメソッドが生えて、使えるようになるのです。
import util.chaining.*
tapped 1
このような不思議な挙動を実現しているのが「暗黙の型変換」です。
tapメソッドが使えるのは暗黙の型変換が働いたから
scala.util.chainingというパッケージには、暗黙の型変換を引き起こすためのscalaUtilChainingOpsというメソッドが含まれています。
scala.util.chainingこれがそのメソッドです。
implicit final def scalaUtilChainingOps[A](a: A): ChainingOps[A]
このメソッドはAという型を受け取って、ChainingOps[A]という型に変換するメソッドです。
このメソッドを使うと、任意の型AをChainingOps[A]に変換することができます。
変換できると、AがあたかもChainingOps[A]が持っているメソッドを使うことができるかのように振る舞うことができます。
scala.util.ChainingOpsChainingOps型について見てみると、このChainingOpsがtapメソッドを持っていたことがわかります。
前述の例で、単なるIntであるはずの1があたかもtapメソッドを持っているかのように見えたのは、
コンパイラがIntからChainingOps[Int]へとこっそり型を変換してくれていたからなのです。
Predefを知る
PredefオブジェクトはScalaにおいて勝手に必ず自動的にインポートされるオブジェクトで、ScalaのコードではどこでもPredefに定義されたコードを使用することができます。
Predefたくさんのimplicit defが定義されていますね。
自動的に別の型へと変換するメソッドをあらかじめ定義しておくことで、まるで型にものすごく多くのメソッドが生えているかのように見えるのです。
今までIDEなどの補完処理において見えていたのは、実は変換先の型が持っているメソッドだったのです。
Predefオブジェクトについてはこちらの記事で詳しく解説しています。
暗黙の型変換は誰にとって嬉しいの?
ライブラリの開発者と使用者向けの機能
暗黙の型変換は、プログラムを書く際に必要以上に型を意識せずに使えるようにできる機能です。
ライブラリは、当該プログラムの書く側よりも使用者や使用回数の方が極端に多いプログラムです。
ライブラリの作者が使用者に対して逐一使い方を教えてあげるわけにはいきません。
使用者が戸惑わないような使い勝手のいいプログラムを書くことが、通常のプログラム開発以上に求められます。
暗黙の型変換は、ライブラリを開発する人がそのライブラリの使い勝手をよくするための機能といっても過言ではないでしょう。
通常の開発では使用を控えましょう
そうでない一般の開発者が良かれと思って使うと混乱のもとになります。
仕組みを見えにくいように隠蔽できる機能であることから、通常の開発現場のような、コミュニケーションコストの比較的低い開発シーンで多用するのはご法度です。
過去数多くの現場で悲劇を生んできた諸刃の剣ですので、使う場合にはよく注意してください。
長いメソッド名の秘密
さて、先程挙げたscalaUtilChainingOpsや、あるいはPredefに定義されていたimplicit defには、何やらとっても長い冗長な名前がついていますね。
実は、これは意図的にそのようにつけられています。
直接呼び出すような使い方はしない前提で定義されており、もし直接呼び出したりするととても不格好となって違和感を感じさせるようにできています。
目につくので後々修正してもらいやすいということですね。
自分で暗黙の型変換を定義する際にはぜひ参考にしてください。